import json

from umqtt.simple import MQTTClient

from beeos import Process, KernelApi
from device_ctl import device_ctl_process, DeviceTask
from log import Log
from rtc import RTCHelper

log = Log(tag='message')
HEARTBEAT_INTERVAL = 600


class MessageProcess(Process):
    def __init__(self, host, port, user, passwd, topic_down, topic_up):
        self.client_id = KernelApi.get_sn()
        log.info('Init message client with:[%s & %s]' % (topic_down, topic_up))
        self.client = None
        self.host = host
        self.port = port
        self.user = user
        self.passwd = passwd
        self.sub_topic = topic_down + self.client_id
        self.pub_topic = topic_up
        log.debug('Subscribe:[%s], And Publish to:[%s]' % (self.sub_topic, self.pub_topic))
        self.loop_count = 0

    def setup(self):
        self._connect()

    def _connect(self):
        self.client = MQTTClient(client_id=self.client_id, server=self.host, port=self.port, user=self.user,
                                 password=self.passwd)
        self.client.set_callback(lambda topic, msg: self._on_msg(topic, msg))
        log.info('Connect[%s:%s] with[%s@%s]' % (self.host, self.port, self.user, self.passwd))
        self.client.connect()
        self.client.subscribe(self.sub_topic)
        device_ctl_process.on_change = lambda status: self._on_change(status)
        log.info('Connected')

    def loop(self):
        try:
            self.client.check_msg()
            self.loop_count += 1
            if self.loop_count >= HEARTBEAT_INTERVAL:
                self.loop_count = 0
                self.send_heartbeat()
        except Exception as e:
            log.error('Error on CHK MQTT MSG', e)
            self.reconnect()
        return False

    def send_heartbeat(self):
        state = device_ctl_process.get_status()
        heartbeat = {
            'type': 'heartbeat',
            'sn': self.client_id,
            'msg': 'Heartbeat',
            'state': state,
        }
        heartbeat = json.dumps(heartbeat)
        log.info('heartbeat:%s' % heartbeat)
        self.client.publish(topic=self.pub_topic, msg=heartbeat, qos=0, retain=False)

    def reconnect(self):
        log.info('reconnect!')
        try:
            self.client.close()
        except Exception as e:
            log.error('Error on disconnect!', e)
        self._connect()

    def finish(self):
        self.client.disconnect()

    def _process_msg(self, msg):
        msg = json.loads(msg)
        action = msg['action']
        if action == 'state_ctl':
            self._process_state_ctl(msg)
        elif action == 'task_ctl':
            self._process_task(msg)
        else:
            log.error('InvAct:%s' % action)

    def _process_task(self, msg):
        payload = msg['payload']
        time = RTCHelper.parse(payload['orderTime'])
        status = payload['status']
        seq = msg['id']
        task = DeviceTask(time, status)
        device_ctl_process.task_context.add_task(task)
        device_status = device_ctl_process.get_status()
        self._reply(seq, 'CheckIt(Task)', device_status)

    def _process_state_ctl(self, msg):
        payload = msg['payload']
        seq = msg['id']
        log.info('MSG:%s:%s' % (seq, payload))
        device_ctl_process.recover()
        device_ctl_process.set_state(payload)
        self._reply(seq, 'CheckIt')

    def _reply(self, seq, msg, status=None):
        if not status:
            status = device_ctl_process.get_status()
        ack_payload = {
            'type': 'ack',
            'seq': seq,
            'state': status,
            'msg': msg,
            'sn': self.client_id,
        }
        ack_payload = json.dumps(ack_payload)
        log.info("ACK:%s" % ack_payload)
        self.client.publish(topic=self.pub_topic, msg=ack_payload, qos=1, retain=True)

    def report_state(self):
        self._reply(-1, 'OPT')

    def _on_msg(self, topic, msg):
        try:
            self._process_msg(msg)
        except Exception as e:
            log.error('ERR_MSG:', e)

    def _on_change(self, status):
        try:
            self._reply(-1, 'RPT', status)
        except Exception as e:
            log.error('ERR S_RPT', e)
